package emediashared.ui;

import java.awt.Desktop;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.dialogs.SearchPattern;
import org.eclipse.ui.part.ViewPart;

import emediashared.SharedActivator;
import emediashared.model.HttpSharedServer;
import emediashared.model.IRemoteFile;
import emediashared.model.ISharedClient;
import emediashared.model.ISharedServer;
import emediashared.model.SharedConstants;
import emediashared.model.SharedHttpClient;
import emediashared.ui.notifications.EventNotifier;
import emediashared.ui.notifications.NotificationType;

public class SharedView extends ViewPart {

	private ISharedServer sharedServer;
	private TableViewer serverViewer;
	private TreeViewer directoryViewer;

	private Map<String, ISharedClient> servers = new HashMap<String, ISharedClient>();
	private String userName;
	private Job serversSyncJob;
	private ISharedClient activeClient;
	private Text filterText;

	
	public void createPartControl(Composite parent) {
		fillToolbar();
		createUI(parent);
		refreshServers();
	}

	private void createUI(Composite parent) {
		SashForm container = new SashForm(parent, SWT.HORIZONTAL | SWT.SMOOTH);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(container);
		GridLayoutFactory.fillDefaults().applyTo(container);

		serverViewer = new TableViewer(container);
		serverViewer.setContentProvider(new ArrayContentProvider());
		serverViewer.setLabelProvider(new LabelProvider() {
			
			public String getText(Object element) {
				if (element instanceof ISharedClient) {
					return ((ISharedClient) element).getName();
				}
				return null;
			}

			
			public Image getImage(Object element) {
				if (element instanceof ISharedClient) {
					ISharedClient sharedClient = (ISharedClient) element;
					if (sharedClient.isConnected()) {
						return SharedConstants.IMAGE_CONNECTED;
					} else {
						return SharedConstants.IMAGE_DISCONNECTED;
					}
				}
				return null;
			}
		});
		serverViewer.setInput(servers.values().toArray());
		serverViewer.setSorter(new ViewerSorter());
		serverViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			
			public void selectionChanged(SelectionChangedEvent event) {
				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
				ISharedClient client = (ISharedClient) selection.getFirstElement();
				if (client != null && client.isConnected()) {
					activeClient = client;
					directoryViewer.setInput(activeClient);
				}
			}
		});
		serverViewer.addDoubleClickListener(new IDoubleClickListener() {
			
			public void doubleClick(DoubleClickEvent event) {
				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
				ISharedClient client = (ISharedClient) selection.getFirstElement();
				connectToClient(client, false);
			}
		});

		Composite rightPane = new Composite(container, SWT.NONE);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(rightPane);
		GridLayoutFactory.fillDefaults().margins(5, 5).applyTo(rightPane);
		
		filterText = new Text(rightPane, SWT.BORDER);
		GridDataFactory.fillDefaults().grab(true, false).applyTo(filterText);
		filterText.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == '\r') {
					directoryViewer.refresh();
				}
			}
		});
		filterText.setToolTipText("Type the filter to be applied and press enter to view the filtered files.");
		
		directoryViewer = new TreeViewer(rightPane);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(directoryViewer.getTree());

		directoryViewer.setContentProvider(getDirectoryContentProvider());
		directoryViewer.setLabelProvider(new LabelProvider() {
			
			public String getText(Object element) {
				if (element instanceof RemoteNode) {
					RemoteNode remoteNode = (RemoteNode) element;
					String text = remoteNode.getName();
					if (remoteNode.isLoading()) {
						text = text + " [Loading...]";
					}
					return text;
				}
				return super.getText(element);
			}

			
			public Image getImage(Object element) {
				if (element instanceof RemoteNode) {
					RemoteNode remoteNode = (RemoteNode) element;
					return remoteNode.isDirectory() ? SharedConstants.IMAGE_FOLDER : remoteNode.isDownloaded() ? SharedConstants.IMAGE_SYNCED_FILE : SharedConstants.IMAGE_FILE;
				}
				return super.getImage(element);
			}
		});
		directoryViewer.setInput(new Object());
		
		directoryViewer.addDoubleClickListener(new IDoubleClickListener() {
			
			public void doubleClick(DoubleClickEvent event) {
				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                 RemoteNode remoteNode = (RemoteNode) selection.getFirstElement();
                 if (directoryViewer.isExpandable(remoteNode)) {
                	 directoryViewer.setExpandedState(remoteNode, !directoryViewer.getExpandedState(remoteNode));
                 } else {
                	 downloadFile(Arrays.asList(remoteNode), activeClient);
                 }
			}
			
		});
		
		directoryViewer.setSorter(new ViewerSorter() {
			@Override
			public int category(Object element) {
				if (element instanceof RemoteNode) {
					RemoteNode remoteNode = (RemoteNode) element;
                    return remoteNode.isDirectory() ? 0 : 1;
				}
				return super.category(element);
			}
		});

		container.setWeights(new int[] { 4, 6 });
		directoryViewer.setFilters(new ViewerFilter[] {new SearchFilter()});
		hookContextMenu(false);
		hookContextMenu(true);
	}
	
	
	private void hookContextMenu(final boolean isDirectory) {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				fillContextMenu(manager, isDirectory);
			}
		});
		Viewer viewer = isDirectory ? directoryViewer : serverViewer;
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	private void fillContextMenu(IMenuManager manager, boolean isDirectory) {
		if (isDirectory) {
			manager.add(new DownloadFilesAction());
			manager.add(new RefreshAction());
		} else {
			manager.add(new ReconnectAction());
		}
	}
	
	private class RefreshAction extends Action {

		public RefreshAction() {
			setText("Refresh");
		}
		
		public void run() {
			RemoteNode node = getNode();
			node.setLoaded(false);
			loadNode(node, activeClient);
		};

		public boolean isEnabled() {
			return getNode().isDirectory();
		};

		private RemoteNode getNode() {
			IStructuredSelection structuredSelection = (IStructuredSelection) directoryViewer.getSelection();
			return (RemoteNode) structuredSelection.getFirstElement();
		}

	};
	
	private class ReconnectAction extends Action {

		public ReconnectAction() {
			setText("Reconnect");
		}
		
		public void run() {
			connectToClient(getClient(), true);
		};

		public boolean isEnabled() {
			return getClient().isConnected();
		};

		private ISharedClient getClient() {
			IStructuredSelection structuredSelection = (IStructuredSelection) serverViewer.getSelection();
			return (ISharedClient) structuredSelection.getFirstElement();
		}

	};
	
	
	private class DownloadFilesAction extends Action {
		public DownloadFilesAction() {
			super("Download");
		}

		public void run() {
			downloadFile(getDownloadableFiles(), activeClient);
		};

		public boolean isEnabled() {
			return !getDownloadableFiles().isEmpty();
		};

		private List<RemoteNode> getDownloadableFiles() {
			List<RemoteNode> files = new ArrayList<RemoteNode>();
			IStructuredSelection structuredSelection = (IStructuredSelection) directoryViewer.getSelection();
			Object[] array = structuredSelection.toArray();
			for (Object object : array) {
				RemoteNode remoteNode = (RemoteNode) object;
				if (remoteNode.isLoaded()) {
					if (remoteNode.isDirectory()) {
						for (RemoteNode nestedRemoteNode : remoteNode.getChildren()) {
							if (!nestedRemoteNode.isDirectory()) {
								if (!files.contains(nestedRemoteNode)) {
									files.add(nestedRemoteNode);
								}
							}
						}
					} else {
						files.add(remoteNode);
					}
				}
			}
			return files;
		}

	};
	protected Properties repoLocations;
	
	
	private class SearchFilter extends ViewerFilter {
		@Override
		public boolean select(Viewer viewer, Object parentElement, Object element) {
			if (element instanceof RemoteNode) {
				RemoteNode remoteNode = (RemoteNode) element;
				if (remoteNode.isDirectory()) {
					if (matches(remoteNode.getName(), filterText.getText())) {
						return true;
					}
					if (remoteNode.isLoaded()) {
						for (RemoteNode nestedNode : remoteNode.getChildren()) {
							if (select(viewer, remoteNode, nestedNode)) {
								return true;
							}
						}
					}
				} else {
					return matches(remoteNode.getName(), filterText.getText()) || matches(remoteNode.getParent().getName(), filterText.getText());
				}
			}
			return false;
		}
		
	}
	
	private void connectToClient(final ISharedClient client, final boolean reconnect) {
		if (client.isConnected() && !reconnect) {
			return;
		}

		Job connectJob = new Job("Connecting to Server: " + client.getName()) {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (!client.isConnected()) {
					if (client.connect()) {
						activeClient = client;
					}
				} else {
					client.reConnect();
				}

				// refresh viewers
				Display.getDefault().asyncExec(new Runnable() {
					@Override
					public void run() {
						if (client.isConnected()) {
							directoryViewer.setInput(client);
							serverViewer.refresh();
						}
					}
				});

				return Status.OK_STATUS;
			}
		};
		connectJob.setUser(true);
		connectJob.schedule();

	}

	private ITreeContentProvider getDirectoryContentProvider() {
		return new ITreeContentProvider() {
			
			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

			}

			
			public void dispose() {

			}

			
			public boolean hasChildren(Object element) {
				if (element instanceof RemoteNode) {
					return ((RemoteNode) element).isDirectory();
				}
				return true;
			}

			
			public Object getParent(Object element) {
				return null;
			}

			
			public Object[] getElements(Object inputElement) {
				if (activeClient != null) {
					if (activeClient.isLoaded()) {
						return activeClient.getChildren().toArray();
					} else {
						loadClientRootNodes(activeClient);
					}
				}
				return new Object[0];
			}

			
			public Object[] getChildren(Object parentElement) {
				if (parentElement instanceof RemoteNode) {
					RemoteNode remoteNode = (RemoteNode) parentElement;
					if (!remoteNode.isLoaded()) {
						loadNode(remoteNode, activeClient);
					}
					return remoteNode.getChildren().toArray();
				}
				return null;
			}

		};
	}

	private void downloadFile(final List<RemoteNode> remoteNodes, final ISharedClient sharedClient) {
		Job loadJob = new Job("Downloading files") {
			
			protected IStatus run(IProgressMonitor monitor) {
				monitor.beginTask("Downloading files from '" + sharedClient.getName() + "'", remoteNodes.size() * 1);
				try {
					for (RemoteNode remoteNode : remoteNodes) {
						monitor.subTask(remoteNode.getName());
						File remoteFile = new File(remoteNode.getFilePath());
						final File targetFolder = new File(PreferencesDialog.getDownloadPath(), remoteFile.getParentFile().getName());
						try {
							if (monitor.isCanceled()) {
								throw new OperationCanceledException();
							}
							sharedClient.downloadFile(remoteNode.getFilePath(), targetFolder.getAbsolutePath(), new SubProgressMonitor(monitor, 1));
							showDownloadMessage(targetFolder, remoteNode);
							remoteNode.setDownloaded(true);
						} catch (Exception e) {
							SharedActivator.showAndLogError(e);
						}
					}
				} finally {
					Display.getDefault().asyncExec(new Runnable() {
						@Override
						public void run() {
							directoryViewer.refresh();
						}
					});
				}
				return Status.OK_STATUS;
			}
		};
		loadJob.setRule(remoteNodes.get(0).getParent());
		loadJob.setUser(true);
		loadJob.schedule();

	};
	
	private void showDownloadMessage(final File targetFolder, RemoteNode remoteNode) {
		Runnable locationRunnable = new Runnable() {
			@Override
			public void run() {
				try {
					Desktop.getDesktop().browse(targetFolder.toURI());
				} catch (Exception e) {
					SharedActivator.getDefault().logException(e);
				}
			}
		};
		String message = "File '" + remoteNode.getName() + "' is successfully copied to repository '" + targetFolder.getName() + "' .";
		EventNotifier.notify("File Added", message, locationRunnable, NotificationType.CONNECTED);
	}
	
	private void loadNode(final RemoteNode parentNode, final ISharedClient sharedClient) {
		Job loadJob = new Job("Loading '" + parentNode.getName() + "'") {
			
			protected IStatus run(IProgressMonitor monitor) {
				parentNode.setLoading(true);
				updateDirectoryViewer(parentNode, false);
				parentNode.getChildren().clear();
				try {
					List<IRemoteFile> files = activeClient.getFiles(parentNode.getFilePath());
					for (IRemoteFile remoteFile : files) {
						parentNode.getChildren().add(new RemoteNode(remoteFile, parentNode));
					}
					parentNode.setLoaded(true);
					updateDirectoryViewer(parentNode, true);
				} catch (RemoteException e) {
					SharedActivator.showAndLogError(e);
					parentNode.setLoaded(false);
					return Status.CANCEL_STATUS;
				} finally {
					parentNode.setLoading(false);
				}
				return Status.OK_STATUS;
			}
		};
		loadJob.setRule(parentNode);
		loadJob.schedule();
	}
	
	private void loadClientRootNodes(final ISharedClient sharedClient) {
		Job loadJob = new Job("Loading '" + sharedClient.getName() + "'") {
			
			protected IStatus run(IProgressMonitor monitor) {
				sharedClient.getChildren();
				updateDirectoryViewer(sharedClient, true);
				return Status.OK_STATUS;
			}
		};
		loadJob.schedule();
	}

	private void updateDirectoryViewer(final Object node, final boolean refresh) {
		Display.getDefault().asyncExec(new Runnable() {
			
			public void run() {
				if (refresh) {
					directoryViewer.refresh();
					directoryViewer.setExpandedState(node, true);
				} else {
					directoryViewer.update(node, null);
				}
			}
		});
	}

	private void refreshServers() {
		userName = PreferencesDialog.getUserName();
		serversSyncJob = new Job("Initializing Servers...") {
			
			protected IStatus run(IProgressMonitor monitor) {
				monitor.beginTask("Trying to set up local server", IProgressMonitor.UNKNOWN);
				sharedServer = HttpSharedServer.getInstance();
				Properties userProperties = new Properties();
			    repoLocations = new Properties();
				try {
					loadProperties(userProperties, PreferencesDialog.getUsersFile());
					loadProperties(repoLocations, PreferencesDialog.getRepoFile());
				} catch (Exception e) {
					SharedActivator.showAndLogError(e);
					return Status.CANCEL_STATUS;
				}
				// remove old username
				userProperties.remove(userName);
				for (Object key : userProperties.keySet()) {
					String otherUser = key.toString();
					String url = userProperties.get(key).toString();
					String repos = repoLocations.getProperty(otherUser);
					ISharedClient sharedClient = new SharedHttpClient(url, otherUser, repos);
					if (!servers.containsKey(otherUser)) {
						servers.put(otherUser, sharedClient);
					} else {
						servers.get(otherUser).resetConfiguration(url, otherUser, repos);
					}
				}

				for (String server : servers.keySet()) {
					if (!userProperties.containsKey(server)) {
						servers.remove(server);
					}
				}
				
				// get the latest user name
				userName = PreferencesDialog.getUserName();
				userProperties.put(userName, sharedServer.getUrl());
				repoLocations.put(userName, PreferencesDialog.getLocalPath());
				try {
					storeProperties(userProperties, PreferencesDialog.getUsersFile());
					storeProperties(repoLocations, PreferencesDialog.getRepoFile());
				} catch (Exception e) {
					SharedActivator.showAndLogError(e);
				}

				Display.getDefault().asyncExec(new Runnable() {
					public void run() {
						serverViewer.setInput(servers.values().toArray());
						directoryViewer.refresh();
					}
				});

				return Status.OK_STATUS;
			}
		};
		serversSyncJob.schedule();

	}

	public static void loadProperties(Properties properties, File file) throws FileNotFoundException, IOException {
		if (file.exists()) {
			FileReader reader = new FileReader(file);
			try {
				properties.load(reader);
			} finally {
				reader.close();
			}
		}
	}

	public static void storeProperties(Properties properties, File file) throws FileNotFoundException, IOException {
		File parentFile = file.getParentFile();
		if (!parentFile.exists()) {
			parentFile.mkdirs();
		}
		FileWriter writer = new FileWriter(file);
		try {
			properties.store(writer, null);
		} finally {
			writer.close();
		}
	}

	
	public void setFocus() {

	}

	private void fillToolbar() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalToolBar(IToolBarManager toolBarManager) {
		
		toolBarManager.add(new RefreshServersAction());
	}

	private class RefreshServersAction extends Action {
		
		public RefreshServersAction() {
			setText("Sync Servers");
			setImageDescriptor(ImageDescriptor.createFromImage(SharedConstants.IMAGE_REFRESH));
		}

		@Override
		public void run() {
			refreshServers();
		}
	}
	
	

	private void fillLocalPullDown(IMenuManager menuManager) {
		menuManager.add(preferencesAction);
	}

	private Action preferencesAction = new Action("Preferences") {
		public void run() {
			PreferencesDialog dialog = new PreferencesDialog();
			if (dialog.open() == IDialogConstants.OK_ID) {
				serversSyncJob.schedule();
			}
			try {
				sharedServer.reconnect();
			} catch (Exception e) {
				SharedActivator.showAndLogError(e);
			}
		};

	};
	
	public static boolean matches(String word, String pattern) {
		SearchPattern searchPattern = new SearchPattern();
		if (!pattern.startsWith("*")) {
			pattern = "*" + pattern;
		}
		searchPattern.setPattern(pattern);
		return searchPattern.matches(word);
	}
}
